﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Obsidian.GameObjects;

namespace Obsidian.Components
{
    public enum CameraClearFlags
    {
        Skybox,
        SolidColor,
        Depth,
        Nothing,
    }

    public enum RenderingPath
    {
        UsePlayerSettings,
        Forward,
        Deferred,
    }

    public enum DepthTextureMode
    {
        None,
        Depth,
        DepthNormals,
    }

    public class Camera : Behavior
    {
        /// <summary>
        /// The field of view of the camera in degrees.
        /// </summary>
        public float FieldOfView { get; set; }

        /// <summary>
        /// The near clipping plane distance.
        /// </summary>
        public float NearClipPlane { get; set; }

        /// <summary>
        /// The far clipping plane distance.
        /// </summary>
        public float FarClipPlane { get; set; }

        /// <summary>
        /// 	Is the camera orthographic (true) or perspective (false)?
        /// </summary>
        public bool Orthographic { get; set; }

        /// <summary>
        /// Camera's half-size when in orthographic mode.
        /// </summary>
        public Vector2 OrthographicSize { get; set; }

        /// <summary>
        /// 	The aspect ratio (width divided by height).
        /// </summary>
        public float AspectRatio { get { return OrthographicSize.X / OrthographicSize.Y; } }

        /// <summary>
        /// The color with which the screen will be cleared.
        /// </summary>
        public Color BackgroundColor { get; set; }

        /// <summary>
        /// Where on the screen is the camera rendered in normalized coordinates.
        /// </summary>
        public RectangleF Rect { get; set; }

        /// <summary>
        /// Where on the screen is the camera rendered in pixel coordinates.
        /// </summary>
        public Rectangle PixelRect { get; set; }

        /// <summary>
        /// How wide is the camera in pixels (Read Only).
        /// </summary>
        public int PixelWidth { get { return PixelRect.Width; } }

        /// <summary>
        /// How tall is the camera in pixels (Read Only).
        /// </summary>
        public int PixelHeight { get { return PixelRect.Height; } }

        /// <summary>
        /// Camera's depth in the camera rendering order.
        /// </summary>
        public int Depth { get; set; }

        /// <summary>
        /// How the camera clears the background.
        /// </summary>
        public CameraClearFlags ClearFlags { get; set; }

        /// <summary>
        /// This is used to render parts of the scene selectively.
        /// </summary>
        public int CullingMask { get; set; }

        /// <summary>
        /// Matrix that transforms from world to camera space.
        /// </summary>
        public Matrix WorldToCameraMatrix { get; set; }

        /// <summary>
        /// Set a custom projection matrix.
        /// </summary>
        public Matrix ProjectionMatrix { get; set; }

        /// <summary>
        /// How and if camera generates a depth texture.
        /// </summary>
        public DepthTextureMode DepthTextureMode { get; set; }

        public Camera()
            :base("Camera")
        {
            Depth = -1;
        }

        /// <summary>
        /// Transforms position from world space into screen space.
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public Vector2 WorldToScreenPoint(Vector2 point)
        {
            Viewport vp = new Viewport(PixelRect);
            Matrix view = Transform.LocalToWorldMatrix;
            var p = vp.Project(new Vector3(point, 0), ProjectionMatrix, view, Matrix.Identity);
            return new Vector2(p.X, p.Y);
        }

        /// <summary>
        /// Transforms position from world space into screen space.
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public Vector3 WorldToScreenPoint(Vector3 point)
        {
            Viewport vp = new Viewport(PixelRect);
            Matrix view = Transform.LocalToWorldMatrix;
            var p = vp.Project(point, ProjectionMatrix, view, Matrix.Identity);
            return p;
        }

        /// <summary>
        /// Transforms position from screen space into world space.
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public Vector2 ScreenToWorldPoint(Vector2 point)
        {
            Viewport vp = new Viewport(PixelRect);
            Matrix view = Transform.LocalToWorldMatrix;
            var p = vp.Unproject(new Vector3(point, 0), ProjectionMatrix, view, Matrix.Identity);
            return new Vector2(p.X, p.Y);
        }

        /// <summary>
        /// Transforms position from screen space into world space.
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public Vector3 ScreenToWorldPoint(Vector3 point)
        {
            Viewport vp = new Viewport(PixelRect);
            Matrix view = Transform.LocalToWorldMatrix;
            var p = vp.Unproject(point, ProjectionMatrix, view, Matrix.Identity);
            return p;
        }

        // TODO - WorldToViewport, ScreenToViewport, etc...

        /// <summary>
        /// Render the camera manually.
        /// </summary>
        public void Render()
        {

        }
    }
}
